/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package mmogle;

import java.net.*;
import java.io.*;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author goia
 */
    public class Servidor{   
        
        
        /* Variaveis de analise */
        
        // Para calcular o tempo médio de cada tipo de atualização 
        
        int     quantidadeAtualizacoesMovimentacao      =  0;
        double  tempoTotalAtualizacoesMovimentacao      = .0;
        
        int     quantidadeAtualizacoesObterItem         =  0;
        double  tempoTotalAtualizacoesObterItem         = .0;
        
        int     quantidadeAtualizacoesEnviarMensagem    =  0;;
        double  tempoTotalAtualizacoesEnviarMensagem    = .0;
        
        
        // Para calcular o tempo total gasto para colocar do cache para o bd
        int     quantidadeAtualizacoesTotal             =  0;;
        double  tempoTotalAtualizacoesTotal             = .0;
        
        
        String JDBC_DRIVER 		= "com.mysql.jdbc.Driver";
        String DATABASE_URL     = "jdbc:mysql://127.0.0.1/MMOGLE";

        public Connection connection 	= null;
        public Statement  statement	= null;

        Cache cache;
        //TabelaMemoriaCache cache;
//        NaiveCache cache;
        public Servidor(String tipoCache){

            criarConexaoBD();
	    
	    if( tipoCache.equals("aplicacao") ){
		cache = new AplicacaoCache();
		trace( "Criando cache baseado em aplicação ");
	    }else if( tipoCache.equals("tabela") ){
		cache    = new TabelaMemoriaCache();
		trace( "Criando cache baseado em tabelas na memóia ");
	    }else{
		cache  = new NaiveCache();
		trace("Nada de cache");
	    }
         
            ExecutorService threadpool = Executors.newFixedThreadPool(2);
            
            /* Socket do servidor */
            threadpool.execute( new Runnable(){
                public void run(){
                    try{
                        
                        ServerSocket serverSocket = null;
                        boolean listening = true;

                        try {
                            serverSocket = new ServerSocket(6666);
                        } catch (IOException e) {
                            System.err.println("Could not listen on port: 6666.");
                            System.exit(-1);
                        }

                    while (listening)
                        new RequisicaoSocket(serverSocket.accept()).start();

                    serverSocket.close();  
                    }catch(IOException e){
                        System.out.println( e.getMessage() );
                    }                                                
                }
            });
            
            /* Cache Baseado em Memória Aplicação */
            threadpool.execute(new Runnable(){
                public void run(){

                    while(true){
                        try{
                            double t1 = System.currentTimeMillis();
                            cache.persistir();
                            double t2 = System.currentTimeMillis();
                            System.out.printf("Salvo no Cache em %f segundos\n", (t2 - t1)/1000 );
                            trace("Esperando 1 segundo para persistir novamente.");
                            Thread.sleep(60*1000);
                        }catch(InterruptedException ie){
                            ie.printStackTrace();
                        }
                    }
                }
            });            
        }

        private void trace( Object o){
            System.out.println ( o.toString() );
        }

        private void criarConexaoBD(){

            trace("Criando Tabelas e Povoando com Avatares");

            try{

                Class.forName(JDBC_DRIVER );
                connection 			= DriverManager.getConnection(DATABASE_URL, "root", "123");
                statement			= connection.createStatement();


                /* Cria as contas utilizadas na simulação */
                statement.execute("TRUNCATE TABLE Mensagem");
                statement.execute("TRUNCATE TABLE Item");
                statement.execute("TRUNCATE TABLE Avatar");
                statement.execute("TRUNCATE TABLE Conta");

                PreparedStatement adicionarConta = connection.prepareStatement("INSERT INTO Conta(idConta,email,senha) VALUES(null,?,?)" ,  Statement.RETURN_GENERATED_KEYS);
                PreparedStatement adicionarAvatar   = connection.prepareStatement("INSERT INTO Avatar(nickname,x,y,Conta_idConta) VALUES(?,?,?,?)");

                for( int i=1; i<=1000; i++){

                    /* criando as contas */
                    adicionarConta.setString(1, "conta"+i);
                    adicionarConta.setString(2, "123");
                    adicionarConta.execute();

                    /* Adiciona o Avatar para aquela conta */
                    adicionarAvatar.setString(1, "avatar"+i);
                    adicionarAvatar.setInt(2,0);
                    adicionarAvatar.setInt(3,0);
                    adicionarAvatar.setInt(4,i);
                    adicionarAvatar.execute();

                }

                trace("Processo concluído.");
                
            }catch(SQLException sqlex){

                System.out.println( sqlex.getMessage() );

            }catch(ClassNotFoundException classNotFoundException){

                System.out.println(classNotFoundException.getMessage());

            }


        }
        /* Thread para atender cada requisição vinda do socket */
  
        class RequisicaoSocket extends Thread {
            private Socket socket = null;

            
            public RequisicaoSocket(Socket socket) {
                System.out.println("Aceitou a requisição.")                ;
                this.socket = socket;
            }

            public void run() {

                String inputLine;
                
                try {
                    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                    BufferedReader in = new BufferedReader(
                                            new InputStreamReader(
                                            socket.getInputStream()));

                    while ((inputLine = in.readLine()) != null) {                        
                      
                        mensagemClienteParser ( inputLine );
                    }
                    out.close();
                    in.close();
                    socket.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        
        
        
        /* Classes de Dados Para o Cache */
        /*
        +-----------------+-------------+------+-----+---------+----------------+
        | Field           | Type        | Null | Key | Default | Extra          |
        +-----------------+-------------+------+-----+---------+----------------+
        | idAvatar        | int(11)     | NO   | PRI | NULL    | auto_increment |
        | nickname        | varchar(45) | YES  | UNI | NULL    |                |
        | x               | int(11)     | YES  |     | NULL    |                |
        | y               | int(11)     | YES  |     | NULL    |                |
        | ultimaAtividade | datetime    | YES  |     | NULL    |                |
        | Conta_idConta   | int(11)     | NO   | MUL | NULL    |                |
        +-----------------+-------------+------+-----+---------+----------------+
        */
        
        class Avatar{
            int idAvatar;
            String nickname;
            int x,y;
            java.util.Date ultimaAtividade;
            int Conta_idConta;
        }
        
        /*  
        +-----------------+-------------+------+-----+---------+----------------+
        | Field           | Type        | Null | Key | Default | Extra          |
        +-----------------+-------------+------+-----+---------+----------------+
        | idItem          | int(11)     | NO   | PRI | NULL    | auto_increment |
        | descricao       | varchar(45) | YES  |     | NULL    |                |
        | Avatar_idAvatar | int(11)     | NO   | MUL | NULL    |                |
        +-----------------+-------------+------+-----+---------+----------------+        
        */
        class Item{
            int idItem;
            String descricao;
            int Avatar_idAvatar;
        }
       
       /*
        +---------------------+--------------+------+-----+---------+----------------+
        | Field               | Type         | Null | Key | Default | Extra          |
        +---------------------+--------------+------+-----+---------+----------------+
        | idMensagem          | int(11)      | NO   | PRI | NULL    | auto_increment |
        | Texto               | varchar(140) | YES  |     | NULL    |                |
        | Avatar_idAvatarDe   | int(11)      | NO   | MUL | NULL    |                |
        | Avatar_idAvatarPara | int(11)      | NO   | MUL | NULL    |                |
        | data                | datetime     | YES  |     | NULL    |                |
        +---------------------+--------------+------+-----+---------+----------------+
        */
        class Mensagem{
            int idMensagem;
            String Texto;
            int Avatar_idAvatarDe;
            int Avatar_idAvatarPara;
            java.util.Date data;
        }
       
        /* Estruturas de Dados para o Cache */
       
        Map<String,Avatar>       AvatarCache     = Collections.synchronizedMap( new HashMap<String,Avatar>() );
        Map<String,Item>         ItemCache       = Collections.synchronizedMap( new HashMap<String,Item>() );
        Map<String,Mensagem>     MensagemCache   = Collections.synchronizedMap( new HashMap<String,Mensagem>() );

	
	interface Cache{
	    public void persistir();
	    public void setAvatar(Avatar __avatar);	    
	}
	
        class NaiveCache implements Cache {

            PreparedStatement atualizarAvatarPreparedStatement;

            public NaiveCache(){
                try{
                    atualizarAvatarPreparedStatement = connection.prepareStatement("UPDATE Avatar SET x = ? , y = ? WHERE nickname like ? ");
                }catch(SQLException e){
                    e.printStackTrace();
                }
            }

            public void setAvatar(Avatar avatar){

                try{

                    atualizarAvatarPreparedStatement.setInt(1, avatar.x);
                    atualizarAvatarPreparedStatement.setInt(2, avatar.y);
                    atualizarAvatarPreparedStatement.setString(3, avatar.nickname);
                    atualizarAvatarPreparedStatement.execute();

                }catch(SQLException e){
                    trace ( e.getMessage() );
                }

            }

            public void persistir(){
                /* Do Nothing! */
            }

        }
        
        class AplicacaoCache implements Cache{
            
            public AplicacaoCache(){
               
            }
            
            public void setAvatar( Avatar avatar ){
             

                if ( !AvatarCache.containsKey( avatar.nickname )) {
                    AvatarCache.remove( avatar.nickname );
                }

                AvatarCache.put (avatar.nickname, avatar);
            }   
            
            public Avatar getAvatar(){
                Avatar avatar = new Avatar();
                
                return avatar;
            }
            
            /* Item */
            public void setItem(){
                
            }
            
            public Item getItem(){
                Item item = new Item();
                
                return item;
            }
            
            /* Mensagem */
            public void setMensagem(){
            
            }
            
            public Mensagem getMensagem(){
                Mensagem mensagem = new Mensagem();
                
                return mensagem;
            }

            public void persistir(){

                PreparedStatement atualizarAvatarPreparedStatement;

                try {
                    /* Preparando declarações SQL para o banco */
                    atualizarAvatarPreparedStatement = connection.prepareStatement("UPDATE Avatar SET x = ? , y = ? WHERE nickname like ? ");
                    // para avatar
                    int avataresAtualizados = 0;

                    // Evitando acesso concorrente para a estrutura
                    synchronized(AvatarCache){

                        for( Avatar avatar : AvatarCache.values() ){
                            
                                avataresAtualizados++;
                                atualizarAvatarPreparedStatement.setInt( 1 , avatar.x );
                                atualizarAvatarPreparedStatement.setInt( 2 , avatar.y );
                                atualizarAvatarPreparedStatement.setString(3, avatar.nickname);

                                //trace ( String.format("UPDATE Avatar SET x = %d , y = %d WHERE nickname like %s" , avatar.x, avatar.y, avatar.nickname) )  ;

                                atualizarAvatarPreparedStatement.execute();
                            
                        }
                    }

                    System.out.println("\n");
                    System.out.printf("Em Avatares: %d atualizados\n", avataresAtualizados);

                }catch(SQLException ex){
                    ex.printStackTrace();
                }
            }
        }        

        
        class TabelaMemoriaCache implements Cache{

            PreparedStatement atualizarAvatarEmCache;
            PreparedStatement atualizarAvatar;
            PreparedStatement inserirAvatar;
            PreparedStatement obterAvatar;
            
            public TabelaMemoriaCache(){

                // Constrói o Cache
                try {

                    statement.execute("DROP TABLE IF EXISTS Avatar_Cache");
                    statement.execute("CREATE TABLE Avatar_Cache ENGINE=MEMORY( SELECT * FROM Avatar LIMIT 0)");


                    atualizarAvatarEmCache  = connection.prepareStatement("UPDATE Avatar_Cache SET x = ? , y = ? WHERE nickname = ?");
                    atualizarAvatar         = connection.prepareStatement("UPDATE Avatar SET x = ? , y = ? WHERE nickname = ?");
                    inserirAvatar           = connection.prepareStatement("INSERT INTO Avatar_Cache(x,y,nickname,Conta_idConta) VALUES(?,?,?,1)");
                    obterAvatar             = connection.prepareStatement("SELECT idAvatar FROM Avatar_Cache WHERE nickname like ?");

                } catch (SQLException ex) {
                   trace ( ex.getMessage() );
                }
                
            }
            
            public void setAvatar(Avatar avatar){
                
                try{

                    // Checa se o avatar existe no cache

                    obterAvatar.setString(1, avatar.nickname);
                    ResultSet rs = obterAvatar.executeQuery();
                    if( rs.next() ){

                        atualizarAvatarEmCache.setInt(1, avatar.x);
                        atualizarAvatarEmCache.setInt(2, avatar.y);
                        atualizarAvatarEmCache.setString(3, avatar.nickname);
                        atualizarAvatarEmCache.execute();

                    }else{

                        /* insere */
                       // statement.execute( String.format("" , avatar.x, avatar.y, avatar.nickname) );
                        inserirAvatar.setInt(1, avatar.x );
                        inserirAvatar.setInt(2, avatar.y );
                        inserirAvatar.setString(3, avatar.nickname );
                        inserirAvatar.execute();
                    }

                }catch(SQLException e){
                    trace ( e.getMessage() );
                }

            }   
            
            public Avatar getAvatar(){
                Avatar avatar = new Avatar();
                
                return avatar;
            }
            
            /* Item */
            public void setItem(){
                
            
            }
            
            public Item getItem(){
                Item item = new Item();
                
                return item;
            }
            
            /* Mensagem */
            public void setMensagem(){
            
            }
            
            public Mensagem getMensagem(){
                Mensagem mensagem = new Mensagem();
                
                return mensagem;
            }   
            
            public void persistir(){
                
                PreparedStatement atualizarAvatarPreparedStatement;

                try {

                    int atualizados     = 0;
                    /* Preparando declarações SQL para o banco */
                    atualizarAvatarPreparedStatement = connection.prepareStatement("UPDATE Avatar SET x = ? , y = ? WHERE nickname like ? ");

                    

//                    ResultSet resultSet = statement.executeQuery("SELECT * FROM Avatar_Cache");
//                    int atualizados     = 0;
//                    Avatar __avatar = new Avatar();
//                    while ( resultSet.next() ){ atualizados++;
//                        __avatar.nickname   = resultSet.getString("nickname");
//                        __avatar.x          = resultSet.getInt("x");
//                        __avatar.y          = resultSet.getInt("y");
//
//                        // Agora atualiza na tabela permanente
//                        atualizarAvatarPreparedStatement.setInt(1, __avatar.x);
//                        atualizarAvatarPreparedStatement.setInt(2, __avatar.y);
//                        atualizarAvatarPreparedStatement.setString(3, __avatar.nickname);
//                        atualizarAvatarPreparedStatement.execute();
//                    }



                    ResultSet rs = statement.executeQuery("call Avatar_Cache_Persistir");
                    if(rs.next()){
                        atualizados = rs.getInt("atualizados");
                    }



                System.out.println("\n");
                System.out.printf("Em Avatares: %d atualizados\n", atualizados);


                }catch(SQLException ex){
                    ex.printStackTrace();
                }
            }
        }     
        
 
        
        // Faz o parsing da mensagem cliente para criar os objetos do cache
        private void mensagemClienteParser( String mensagem ){
            
            Pattern p = Pattern.compile(";");
            
            // Se for uma atualização de posição                        
            if ( mensagem.matches("CHARMOV.*") ){               
                String[] tokens = p.split(mensagem);
                
                String nickname = tokens[1];
                int x           = Integer.parseInt( tokens[2] );
                int y           = Integer.parseInt( tokens[3] );
                
                /* Coloca as novas posiç~oes no cache */
                Avatar __avatar         = new Avatar();
                __avatar.nickname       = nickname;
                __avatar.x              = x;
                __avatar.y              = y;

                //trace( String.format("%s está movimentando-se para %d %d", __avatar.nickname, __avatar.x, __avatar.y) );
                
                cache.setAvatar( __avatar );
                
            }           
            
            // Se for uma aquisição de item            
            if ( mensagem.matches("GETINV.*") ){               
                String[] tokens = p.split(mensagem);
                
                String nickname = tokens[1];
                String item     = tokens[2];
                
                //System.out.printf(  "Avatar %s obteve um o item %s \n" , nickname, item );
            }
            
            
            // Se for uma mensagem do chat     
            if ( mensagem.matches("SNDMSG.*") ){               
                String[] tokens = p.split(mensagem);
                
                String nickname = tokens[1];
                String para     = tokens[2];
                String msg      = tokens[3];
                
//                 System.out.printf(  "Avatar %s mandou uma mensagem para %s \n" , nickname, para );
            }            
            
        }
        
        

        public static void main(String[] args){
	    String tipoCache = new String("");
	    if( args.length > 0 )
		tipoCache = args[0];
	    
		Servidor servidor = new Servidor( tipoCache );            
        }

    }